package com.bkhech.filter;


import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import com.bkhech.grace.result.ResponseStatusEnum;
import com.bkhech.utils.IPUtil;
import com.bkhech.utils.RedisOperator;
import com.bkhech.utils.RenderErrorMsgUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * IPLimitFilter
 * 流量限制
 * <p>
 * 需求：例如，判断某个一请求的 ip 在某个（15s）时间段内，请求次数超过一定次数（5次），如果超过，则限制访问一段时间（20s），之后才能够继续恢复访问
 *
 * @author guowm
 * @date 2024-11-04
 */
@Component
@Slf4j
@RefreshScope // 动态刷新
public class IPLimitFilter implements GlobalFilter, Ordered {
    @Resource
    private RedisOperator redisOperator;

    @Value("${black-ip.timeInterval}")
    private Integer timeInterval;

    @Value("${black-ip.limitTimes}")
    private Integer limitTimes;

    @Value("${black-ip.continueCounts}")
    private Integer continueCounts;

    @Override
    @RefreshScope
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//        log.info("timeInterval：{}", timeInterval);
//        log.info("limitTimes：{}", limitTimes);
//        log.info("continueCounts：{}", continueCounts);
        return doLimit(exchange, chain);
    }


    private Mono<Void> doLimit(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String ip = IPUtil.getIP(request);

        final String ipRedisKey = Stream.of("gateway-ip", ip).collect(Collectors.joining(StrUtil.COLON));
        // 黑名单的redisKey
        final String blackIpRedisKey = Stream.of("gateway-ip", "limit", ip).collect(Collectors.joining(StrUtil.COLON));

        // 获取当前的ip并且查询还剩下多少时间，如果时间大于0，则表示还在黑名单中
        long leftTime = redisOperator.ttl(blackIpRedisKey);
        if (leftTime > 0) {
            return RenderErrorMsgUtil.renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP);
        }

        long requestCounts = redisOperator.increment(ipRedisKey, 1);
        // 判段是否是第一次请求,如果是给一个续期时间
        if (requestCounts == 1L) {
            redisOperator.expire(ipRedisKey, timeInterval);
        }

        //如果在续期时间内请求次数大于限制的连续请求数，那么设置为黑名单IP
        if (requestCounts > continueCounts) {
            redisOperator.set(blackIpRedisKey, blackIpRedisKey, limitTimes);
            return RenderErrorMsgUtil.renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP);
        }

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
